﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Globalization;
using OpenGameEngine.Logging;
using System.Threading;

namespace OpenGameEngine.Net.Server
{
    public class ServerClientBase
    {
        public Queue<IPacket> SendQueue {get;set;}
        public ServerBase Server { get; set; }
        public short Ping { get; set; }
        public String Username { get; set; }
        public String Hostname { get; set; }
        public bool IsLoggedIn { get; set; }
        public CultureInfo Locale { get; set; }
        public NetworkStream NetworkStream { get; set; }
        public GameStream Stream { get; set; }

        internal string AuthenticationHash;
        
        internal bool EncryptionEnabled;
        internal byte[] SharedKey;
        internal TcpClient TcpClient;

        public bool Connected { get { return TcpClient.Connected; } }

        internal ServerClientBase()
        {
            SendQueue = new Queue<IPacket>();
            IsLoggedIn = false;
            EncryptionEnabled = false;
            Locale = CultureInfo.CurrentCulture;
        }

        public ServerClientBase(TcpClient tcp, ServerBase server) : this()
        {
            TcpClient = tcp;
            NetworkStream = tcp.GetStream();
            tcp.ReceiveBufferSize = 32768;
            Stream = new GameStream(tcp.GetStream());
            Server = server;
        }

        public virtual void SendPacket(IPacket packet)
        {
            if (packet == null)
                throw new ArgumentNullException("packet");

            SendQueue.Enqueue(packet);
        }

        public virtual void DoClientUpdates()
        {

        }

        public IAsyncResult BeginHandleClient(AsyncCallback cb, object obj)
        {
            AsyncHandler asyncHanlder = new AsyncHandler(HandleClientAsync);
            AsyncState asyncState = new AsyncState(asyncHanlder, obj);

            return asyncHanlder.BeginInvoke(cb, asyncState);
        }

        public void EndHandleClient(IAsyncResult result)
        {
            AsyncState async = result.AsyncState as AsyncState;
            AsyncHandler del = async.Delegate;

            del.EndInvoke(result);
        }

        private void HandleClientAsync() 
        {
            if (this.IsLoggedIn)
                DoClientUpdates();

            bool disconnect = false;
            while (this.SendQueue.Count != 0)
            {
                IPacket packet = this.SendQueue.Dequeue();
                if (packet != null)
                {
                    try
                    {
                        packet.WritePacket(this.Stream);

                        LogProvider.Log("Info: writing " + packet.ToString(), LogImportance.Low);

                        this.Stream.Flush();

                    }
                    catch (Exception e)
                    {
                        LogProvider.Log("Error: " + e.Message + "\n" + e.StackTrace, LogImportance.High);
                        disconnect = true;
                    }
                }
                Thread.Sleep(1);
            }

            DateTime readTimeout = DateTime.Now.AddMilliseconds(100);
            if (this.NetworkStream.DataAvailable && DateTime.Now < readTimeout)
            {
                try
                {
                    IPacket packet = PacketReader.ReadPacket(this.Stream);
                    LogProvider.Log("Info: reading " + packet.ToString(), LogImportance.Low);
                    Server.HandlePacket(this, packet);
                }
                catch (Exception e)
                {
                    LogProvider.Log("Error: " + e.Message + "\n" + e.StackTrace);
                }
            }

            if (disconnect)
            {
                Server.OnClientDisconnected(this, " packet exception");
                return;
            }
        }
    }
}
